using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Oops.Xna.Framework.Input
{
	public class InputComponent : GameComponent, IInputService 
	{
		private KeyboardState currentKeyboardState, previousKeyboardState;
#if !XBOX
		private bool keepMouseCaptured;
		private MouseState currentMouseState, previousMouseState;
#endif
		private GamePadDeadZone deadZone;
		private float thumbStickEase = 1.0f;
		private float triggerEase = 1.0f;

		private GamePadState[] currentGamePadState = new GamePadState[4];
		private GamePadState[] previousGamePadState = new GamePadState[4];

		private Vector2[] currentLeftThumbStick = new Vector2[4];
		private Vector2[] previousLeftThumbStick = new Vector2[4];
		private Vector2[] currentRightThumbStick = new Vector2[4];
		private Vector2[] previousRightThumbStick = new Vector2[4];

		private float[] currentLeftTrigger = new float[4];
		private float[] previousLeftTrigger = new float[4];
		private float[] currentRightTrigger = new float[4];
		private float[] previousRightTrigger = new float[4];
						
		/// <summary>
		/// Initializes a new instance of the <see cref="InputComponent"/> class.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/> that the game component should be attached to.</param>
		public InputComponent(Game game) : base(game) 
		{
			// The game parameter must be specified.
			if (game == null)
			{
				throw new ArgumentNullException("game");
			}
		}

		#region IInputService Members

		public bool IsKeyUp(Keys key) 
		{ 
			return this.currentKeyboardState.IsKeyUp(key); 
		}

		public bool IsKeyDown(Keys key) 
		{ 
			return this.currentKeyboardState.IsKeyDown(key); 
		}

		public bool IsKeyPressed(Keys key) 
		{ 
			return this.currentKeyboardState.IsKeyDown(key) && this.previousKeyboardState.IsKeyUp(key); 
		}

		public bool IsKeyReleased(Keys key) 
		{ 
			return this.currentKeyboardState.IsKeyUp(key) && this.previousKeyboardState.IsKeyDown(key);
		}
#if !XBOX
		public bool KeepMouseCaptured 
		{
			get { return this.keepMouseCaptured; }
			set { this.keepMouseCaptured = value; } 
		}

		public int MouseX 
		{ 	
			get { return this.currentMouseState.X; }
		}

		public int MouseY
		{
			get { return this.currentMouseState.Y; }
		}

		public int MouseXDelta
		{
			get { return this.previousMouseState.X - this.currentMouseState.X; }
		}

		public int MouseYDelta
		{
			get { return this.previousMouseState.Y - this.currentMouseState.Y; }
		}

		public int MouseWheel
		{
			get { return this.currentMouseState.ScrollWheelValue; }
		}

		public int MouseWheelDelta
		{
			get { return this.previousMouseState.ScrollWheelValue - this.currentMouseState.ScrollWheelValue; }
		}

		public void SetMousePosition(int x, int y)
		{
			Mouse.SetPosition(x, y);
		}

		public bool IsMouseButtonUp(MouseButtons button) 
		{
			return (this.GetMouseButtonsUp() & button) == button;
		}

		public bool IsMouseButtonDown(MouseButtons button) 
		{
			return (this.GetMouseButtonsDown() & button) == button;
		}

		public bool IsMouseButtonPressed(MouseButtons button) 
		{
			return (this.GetMouseButtonsPressed() & button) == button;
		}

		public bool IsMouseButtonReleased(MouseButtons button) 
		{
			return (this.GetMouseButtonsReleased() & button) == button;
		}

		public MouseButtons GetMouseButtonsUp()
		{
			MouseButtons result = MouseButtons.None;

			if (this.currentMouseState.LeftButton == ButtonState.Released)
			{
				result |= MouseButtons.Left;
			}
			
			if (this.currentMouseState.RightButton == ButtonState.Released)
			{
				result |= MouseButtons.Right;
			}
			
			if (this.currentMouseState.MiddleButton == ButtonState.Released)
			{
				result |= MouseButtons.Middle;
			}

			return result;
		}

		public MouseButtons GetMouseButtonsDown()
		{
			MouseButtons result = MouseButtons.None;

			if (this.currentMouseState.LeftButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Left;
			}
			
			if (this.currentMouseState.RightButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Right;
			}
			
			if (this.currentMouseState.MiddleButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Middle;
			}
			
			return result;
		}

		public MouseButtons GetMouseButtonsPressed()
		{
			MouseButtons result = MouseButtons.None;

			if (this.currentMouseState.LeftButton == ButtonState.Pressed && this.previousMouseState.LeftButton == ButtonState.Released)
			{
				result |= MouseButtons.Left;
			}
			
			if (this.currentMouseState.RightButton == ButtonState.Pressed && this.previousMouseState.RightButton == ButtonState.Released)
			{
				result |= MouseButtons.Right;
			}
			
			if (this.currentMouseState.MiddleButton == ButtonState.Pressed && this.previousMouseState.MiddleButton == ButtonState.Released)
			{
				result |= MouseButtons.Middle;
			}
			
			return result;
		}

		public MouseButtons GetMouseButtonsReleased()
		{
			MouseButtons result = MouseButtons.None;

			if (this.currentMouseState.LeftButton == ButtonState.Released && this.previousMouseState.LeftButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Left;
			}
			
			if (this.currentMouseState.RightButton == ButtonState.Released && this.previousMouseState.RightButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Right;
			}
			
			if (this.currentMouseState.MiddleButton == ButtonState.Released && this.previousMouseState.MiddleButton == ButtonState.Pressed)
			{
				result |= MouseButtons.Middle;
			}
			
			return result;
		}
#endif
		public GamePadDeadZone ThumbStickDeadZone 
		{
			get { return this.deadZone; }
			set { this.deadZone = value; } 
		}

		public float ThumbStickEase
		{
			get { return this.thumbStickEase; }
			set { this.thumbStickEase = value; }
		}

		public float TriggerEase
		{
			get { return this.triggerEase; }
			set { this.triggerEase = value; }
		}

		public bool IsGamePadConnecting(int index) 
		{ 
			return this.currentGamePadState[index].IsConnected && !this.previousGamePadState[index].IsConnected; 
		}

		public bool IsGamePadDisconnecting(int index) 
		{ 
			return !this.currentGamePadState[index].IsConnected && this.previousGamePadState[index].IsConnected; 
		}

		public bool IsGamePadConnected(int index) 
		{ 
			return this.currentGamePadState[index].IsConnected; 
		}

		public Vector2 GetGamePadLeftThumbStick(int index) 
		{ 
			return this.currentLeftThumbStick[index]; 
		}

		public Vector2 GetGamePadLeftThumbStickPrevious(int index) 
		{ 
			return this.previousLeftThumbStick[index]; 
		}

		public Vector2 GetGamePadLeftThumbStickDelta(int index) 
		{ 
			return this.previousLeftThumbStick[index] - this.currentLeftThumbStick[index]; 
		}

		public Vector2 GetGamePadRightThumbStick(int index) 
		{ 
			return this.currentRightThumbStick[index];
		}

		public Vector2 GetGamePadRightThumbStickPrevious(int index) 
		{ 
			return this.previousRightThumbStick[index];
		}

		public Vector2 GetGamePadRightThumbStickDelta(int index) 
		{ 
			return this.previousRightThumbStick[index] - this.currentRightThumbStick[index]; 
		}

		public float GetGamePadLeftTrigger(int index) 
		{ 
			return this.currentLeftTrigger[index];
		}

		public float GetGamePadLeftTriggerDelta(int index) 
		{ 
			return this.previousLeftTrigger[index] - this.currentLeftTrigger[index]; 
		}

		public float GetGamePadRightTrigger(int index) 
		{ 
			return this.previousRightTrigger[index];
		}

		public float GetGamePadRightTriggerDelta(int index) 
		{ 
			return this.previousRightTrigger[index] - this.currentRightTrigger[index]; 
		}

		public bool IsGamePadButtonUp(int index, Buttons buttons)
		{
			return this.currentGamePadState[index].IsButtonUp(buttons);
		}

		public bool IsGamePadButtonDown(int index, Buttons buttons)
		{
			return this.currentGamePadState[index].IsButtonDown(buttons);
		}

		public bool IsGamePadButtonPressed(int index, Buttons button)
		{
			return this.currentGamePadState[index].IsButtonDown(button) && this.previousGamePadState[index].IsButtonUp(button);
		}

		public bool IsGamePadButtonReleased(int index, Buttons button)
		{
			return this.currentGamePadState[index].IsButtonUp(button) && this.previousGamePadState[index].IsButtonDown(button);
		}

		#endregion

		#region GameComponent Members

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Remove self from the service container.
					if (this.Game.GetService<IInputService>(false) == this)
					{
						this.Game.RemoveService<IInputService>(true);
					}
				}
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Add self to the service container.
			this.Game.AddService<IInputService>(this, true);

			// Get the initial input states.
			this.currentKeyboardState = Keyboard.GetState();
#if !XBOX
			this.currentMouseState = Mouse.GetState();
#endif
			this.currentGamePadState[0] = GamePad.GetState(PlayerIndex.One, this.deadZone);
			this.currentGamePadState[1] = GamePad.GetState(PlayerIndex.Two, this.deadZone);
			this.currentGamePadState[2] = GamePad.GetState(PlayerIndex.Three, this.deadZone);
			this.currentGamePadState[3] = GamePad.GetState(PlayerIndex.Four, this.deadZone);
			for (int i = 0; i < 4; i++)
			{
				this.ApplyThumbStickEase(this.currentGamePadState[i].ThumbSticks.Left, ref this.currentLeftThumbStick[i]);
				this.ApplyThumbStickEase(this.currentGamePadState[i].ThumbSticks.Right, ref this.currentRightThumbStick[i]);

				this.currentLeftTrigger[i] = this.ApplyTriggerEase(this.currentGamePadState[i].Triggers.Left);
				this.currentRightTrigger[i] = this.ApplyTriggerEase(this.currentGamePadState[i].Triggers.Right);
			}

			// Call inherited method.
			base.Initialize();
		}

		#endregion

		#region IUpdateable

		public override void Update(GameTime gameTime)
		{
			// Call inherited method.
			base.Update(gameTime);	

			// Update this frame's keyboard state.
			this.previousKeyboardState = this.currentKeyboardState;
			this.currentKeyboardState = Keyboard.GetState();
#if !XBOX
			// Update this frame's mouse state.
			this.previousMouseState = this.currentMouseState;
			this.currentMouseState = Mouse.GetState();

			// Move the mouse back to the center of the client bounds.
			if (this.keepMouseCaptured)
			{
				Mouse.SetPosition(this.Game.Window.ClientBounds.Width >> 1, this.Game.Window.ClientBounds.Height >> 1);
			}
#endif
			// Update this frame's gamepad state.
			for (int i = 0; i < 4; i++)
			{
				this.previousGamePadState[i] = this.currentGamePadState[i];

				this.previousLeftThumbStick[i] = this.currentLeftThumbStick[i];
				this.previousRightThumbStick[i] = this.currentRightThumbStick[i];

				this.previousLeftTrigger[i] = this.currentLeftTrigger[i];
				this.previousRightTrigger[i] = this.currentRightTrigger[i];
			}
			this.currentGamePadState[0] = GamePad.GetState(PlayerIndex.One, this.deadZone);
			this.currentGamePadState[1] = GamePad.GetState(PlayerIndex.Two, this.deadZone);
			this.currentGamePadState[2] = GamePad.GetState(PlayerIndex.Three, this.deadZone);
			this.currentGamePadState[3] = GamePad.GetState(PlayerIndex.Four, this.deadZone);
			for (int i = 0; i < 4; i++)
			{
				this.ApplyThumbStickEase(this.currentGamePadState[i].ThumbSticks.Left, ref this.currentLeftThumbStick[i]);
				this.ApplyThumbStickEase(this.currentGamePadState[i].ThumbSticks.Right, ref this.currentRightThumbStick[i]);

				this.currentLeftTrigger[i] = this.ApplyTriggerEase(this.currentGamePadState[i].Triggers.Left);
				this.currentRightTrigger[i] = this.ApplyTriggerEase(this.currentGamePadState[i].Triggers.Right);
			}
		}

		#endregion

		private void ApplyThumbStickEase(Vector2 value, ref Vector2 result)
		{
			result.X = (float)Math.Pow(Math.Abs(value.X), this.thumbStickEase) * Math.Sign(value.X);
			result.Y = (float)Math.Pow(Math.Abs(value.Y), this.thumbStickEase) * Math.Sign(value.Y);
		}

		private float ApplyTriggerEase(float value)
		{
			return (float)Math.Pow(Math.Abs(value), this.triggerEase) * Math.Sign(value);
		}
	}
}


